/*
 * File:    SoapResponse.java
 * Created: 09-Dec-2007
 * Version: $Id$
 *
 * COPYRIGHT (C) 2007, Bitgate Software, LLC.  All Rights Reserved
 * Released under the Creative Commons License version 3.0
 *
 * software@bitgatesoftware.com
 */

package com.bitgate.util.soap;

import java.lang.reflect.Method;

import static com.bitgate.util.debug.Debug.debug;
import static com.bitgate.util.debug.Debug.isDebugEnabled;

/**
 * This is a SOAP response object, which generates a response to a called SOAP function, or a failure, depending on the
 * action that was performed.
 * 
 * @author kenji
 * @since 0.7.0
 */
public class SoapResponse
{
	/**
	 * Generates a SOAP failure response.
	 * 
	 * @param faultType <code>String</code> containing the fault type.
	 * @param faultError <code>String</code> containing the fault error message.
	 * @param responseDetails <code>String</code> containing extra XML data for the response details.
	 * @return <code>String</code> containing the overall failure message to send back.
	 */
	public static String generateFailure(String faultType, String faultError, String responseDetails) 
	{
		StringBuffer sBuffer = new StringBuffer();
		
		sBuffer.append("<SOAP-ENV:Envelope xmlns:SOAP-ENV=\"http://schemas.xmlsoap.org/soap/envelope/\">\n")
			   .append("    <SOAP-ENV:Body>\n")
			   .append("\t<SOAP-ENV:Fault>\n")
			   .append("\t    <faultcode>")
			   .append(faultType)
			   .append("</faultcode>\n")
			   .append("\t    <faultstring>")
			   .append(faultError)
			   .append("</faultstring>\n")
			   .append("\t    <detail>\n")
			   .append((responseDetails != null) ? responseDetails : "")
			   .append("\t    </detail>\n")
			   .append("\t</SOAP-ENV:Fault>\n")
			   .append("    </SOAP-ENV:Body>\n")
			   .append("</SOAP-ENV:Envelope>\n");
		
		return sBuffer.toString();
	}
	
	private static String generateSchema(Class<?> soapClass, String host, String url, boolean extended)
	{
		StringBuffer sBuffer = new StringBuffer();
		StringBuffer eBuffer = new StringBuffer();

		if (extended) {
			sBuffer.append("    <wsdl:types xmlns:http=\"http://schemas.xmlsoap.org/wsdl/http/\"\n")
			   	   .append("\txmlns:soap=\"http://schemas.xmlsoap.org/wsdl/soap/\"\n")
			   	   .append("\txmlns:s=\"http://www.w3.org/2001/XMLSchema\"\n")
			   	   .append("\txmlns:soapenc=\"http://schemas.xmlsoap.org/soap/encoding/\"\n")
			   	   .append("\txmlns:tns=\"http://")
			   	   .append(host)
			   	   .append("/\"\n")
			   	   .append("\txmlns:tm=\"http://microsoft.com/wsdl/mime/textMatching/\"\n")
			   	   .append("\txmlns:mime=\"http://schemas.xmlsoap.org/wsdl/mime/\"\n")
			   	   .append("\ttargetNamespace=\"http://")
			   	   .append(host)
			   	   .append("/\"\n")
			   	   .append("\txmlns:wsdl=\"http://schemas.xmlsoap.org/wsdl/\">\n")
			   	   .append("\t<s:schema elementFormDefault=\"qualified\" targetNamespace=\"http://")
			   	   .append(host)
			   	   .append("/\">\n");
		} else {
			sBuffer.append("    <wsdl:types>\n")
				   .append("\t<s:schema elementFormDefault=\"qualified\" targetNamespace=\"http://")
				   .append(host)
				   .append("/\">\n");
		}
		
		Method methods[] = soapClass.getDeclaredMethods();
		
		for(Method method : methods) {
			// Schema request.
			
			sBuffer.append("\t    <s:element name=\"Get")
				   .append(method.getName())
				   .append("Request\">\n")
				   .append("\t\t<s:complexType>\n")
				   .append("\t\t    <s:sequence>\n");
			
			Class<?> parameters[] = method.getParameterTypes();
			int counter = 0;
			
			for(Class<?> parameter : parameters) {
				sBuffer.append("\t\t\t<s:element minOccurs=\"0\" maxOccurs=\"")
					   .append(parameter.isArray() ? "unbounded" : "1")
					   .append("\" name=\"input")
					   .append(counter++)
					   .append("\" type=\"tns:");
				
				if (parameter.getName().contains("String")) {
					sBuffer.append("String");
				} else if (parameter.getName().contains("Float")) {
					sBuffer.append("Float");
				} else if (parameter.getName().contains("Double")) {
					sBuffer.append("Double");
				} else if (parameter.getName().contains("Integer")) {
					sBuffer.append("Integer");
				} else if (parameter.isEnum()) {
					Object enums[] = parameter.getEnumConstants();
					String enumName = parameter.getName();
					
					enumName = enumName.substring(enumName.lastIndexOf("$") + 1);
					
					eBuffer.append("\t    <s:simpleType name=\"GetEnum")
						   .append(enumName)
						   .append("\" base=\"s:string\">\n");
					
					for(Object enumEntry : enums) {
						eBuffer.append("\t\t<s:enumeration value=\"")
							   .append(enumEntry.toString())
							   .append("\" />\n");
					}
					
					eBuffer.append("\t    </s:simpleType>\n\n");
					
					sBuffer.append("GetEnum")
						   .append(enumName);
				} else {
					String simpleName = parameter.getName();
					
					if (simpleName.indexOf("$") != -1) {
						simpleName = simpleName.substring(simpleName.lastIndexOf("$") + 1);
					}
					
					if (simpleName.indexOf(";") != -1) {
						simpleName = simpleName.substring(0, simpleName.indexOf(";"));
					}
					
					sBuffer.append("GetEnum")
						   .append(simpleName);
				}
				
				sBuffer.append("\" />\n");
			}
			
			sBuffer.append("\t\t    </s:sequence>\n")
				   .append("\t\t</s:complexType>\n")
				   .append("\t    </s:element>\n\n");
			
			// Schema Response.
			
			sBuffer.append("\t    <s:element name=\"Get")
				   .append(method.getName())
				   .append("Response\">\n")
				   .append("\t\t<s:complexType>\n")
				   .append("\t\t    <s:sequence>\n");
			
			Class<?> returnClass = method.getReturnType();
			
			sBuffer.append("\t\t\t<s:element minOccurs=\"1\" maxOccurs=\"1\" name=\"response\" type=\"tns:");
			
			if (returnClass.getName().contains("String")) {
				sBuffer.append("String");
			} else if (returnClass.getName().contains("Float")) {
				sBuffer.append("Float");
			} else if (returnClass.getName().contains("Double")) {
				sBuffer.append("Double");
			} else if (returnClass.getName().contains("Integer")) {
				sBuffer.append("Integer");
			} else {
				String simpleName = returnClass.getName();
				
				if (simpleName.indexOf("$") != -1) {
					simpleName = simpleName.substring(simpleName.lastIndexOf("$") + 1);
				}
				
				if (simpleName.indexOf(";") != -1) {
					simpleName = simpleName.substring(0, simpleName.indexOf(";"));
				}
				
				sBuffer.append("GetEnum")
					   .append(simpleName);
			}
			
			sBuffer.append("\" />\n")
				   .append("\t\t    </s:sequence>\n")
				   .append("\t\t</s:complexType>\n")
				   .append("\t    </s:element>\n\n");
		}
		
		// TNS Standard Elements
		
		sBuffer.append("\t    <s:element name=\"String\" type=\"s:string\" />\n")
			   .append("\t    <s:element name=\"Float\" type=\"s:float\" />\n")
			   .append("\t    <s:element name=\"Double\" type=\"s:double\" />\n")
			   .append("\t    <s:element name=\"Integer\" type=\"s:integer\" />\n")
			   .append(eBuffer)
			   .append("\t</s:schema>\n")
			   .append("    </wsdl:types>\n");
		
		return sBuffer.toString();
	}
	
	private static String generateMessages(Class<?> soapClass, String host, String url)
	{
		StringBuffer sBuffer = new StringBuffer();
		Method methods[] = soapClass.getDeclaredMethods();
		
		for(Method method : methods) {
			sBuffer.append("    <wsdl:message name=\"Get")
				   .append(method.getName())
				   .append("SoapIn\">\n")
				   .append("\t<wsdl:part name=\"parameters\" element=\"tns:Get")
				   .append(method.getName())
				   .append("Request\" />\n")
				   .append("    </wsdl:message>\n\n")
				   .append("    <wsdl:message name=\"Get")
				   .append(method.getName())
				   .append("SoapOut\">\n")
				   .append("\t<wsdl:part name=\"parameters\" element=\"tns:Get")
				   .append(method.getName())
				   .append("Response\" />\n")
				   .append("    </wsdl:message>\n\n");
		}
	
		return sBuffer.toString();
	}
	
	private static String generatePorts(Class<?> soapClass, String host, String url)
	{
		StringBuffer sBuffer = new StringBuffer();
		String simpleName = soapClass.getName();
		Method methods[] = soapClass.getDeclaredMethods();
		SoapQuery sQuery = new SoapQuery();
		
		if (simpleName.indexOf(".") != -1) {
			simpleName = simpleName.substring(simpleName.lastIndexOf(".") + 1);
		}
		
		sBuffer.append("    <wsdl:portType name=\"")
			   .append(simpleName)
			   .append("Soap\">\n");
		
		for(Method method : methods) {
			try {
				sBuffer.append("\t<wsdl:operation name=\"Get")
					   .append(method.getName())
					   .append("\">\n")
					   .append("\t    <documentation>")
					   .append(sQuery.getMethodDocumentation(soapClass, method.getName()))
					   .append("</documentation>\n")
					   .append("\t    <wsdl:input message=\"tns:Get")
					   .append(method.getName())
					   .append("SoapIn\" />\n")
					   .append("\t    <wsdl:output message=\"tns:Get")
					   .append(method.getName())
					   .append("SoapOut\" />\n")
					   .append("\t</wsdl:operation>\n\n");
			} catch(SoapException e) {
				if (isDebugEnabled()) {
					debug("Exception during binding creation: " + e.getMessage());
				}
			}
		}
		
		sBuffer.append("    </wsdl:portType>\n");
		
		return sBuffer.toString();
	}
	
	private static String generateBindings(Class<?> soapClass, String host, String url)
	{
		StringBuffer sBuffer = new StringBuffer();
		String simpleName = soapClass.getName();
		Method methods[] = soapClass.getDeclaredMethods();
		
		if (simpleName.indexOf(".") != -1) {
			simpleName = simpleName.substring(simpleName.lastIndexOf(".") + 1);
		}
		
		sBuffer.append("    <wsdl:binding name=\"")
			   .append(simpleName)
			   .append("Soap\" type=\"tns:")
			   .append(simpleName)
			   .append("Soap\">\n")
			   .append("\t<soap:binding transport=\"http://schemas.xmlsoap.org/soap/http\" style=\"document\" />\n");
		
		for(Method method : methods) {
			sBuffer.append("\t<wsdl:operation name=\"Get")
				   .append(method.getName())
				   .append("\">\n")
				   .append("\t    <soap:operation soapAction=\"http://")
				   .append(host)
				   .append(url)
				   .append("/")
				   .append(method.getName())
				   .append("\" style=\"document\" />\n")
				   .append("\t    <wsdl:input>\n")
				   .append("\t\t<soap:body use=\"literal\" />\n")
				   .append("\t    </wsdl:input>\n\n")
				   .append("\t    <wsdl:output>\n")
				   .append("\t\t<soap:body use=\"literal\" />\n")
				   .append("\t    </wsdl:output>\n")
				   .append("\t</wsdl:operation>\n\n");
		}
		
		sBuffer.append("    </wsdl:binding>\n");
		
		return sBuffer.toString();
	}
	
	private static String generateService(Class<?> soapClass, String host, String url)
	{
		StringBuffer sBuffer = new StringBuffer();
		String simpleName = soapClass.getName();
		SoapQuery sQuery = new SoapQuery();
		
		if (simpleName.indexOf(".") != -1) {
			simpleName = simpleName.substring(simpleName.lastIndexOf(".") + 1);
		}
		
		try {
			sBuffer.append("    <wsdl:service name=\"")
				   .append(simpleName)
				   .append("\">\n")
				   .append("\t<documentation xmlns=\"http://schemas.xmlsoap.org/wsdl/\">")
				   .append(sQuery.getDocumentation(soapClass))
				   .append("</documentation>\n\n")
				   .append("\t<wsdl:port name=\"")
				   .append(simpleName)
				   .append("Soap\" binding=\"tns:")
				   .append(simpleName)
				   .append("Soap\">\n")
				   .append("\t    <soap:address location=\"http://")
				   .append(host)
				   .append(url)
				   .append("\" />\n")
				   .append("\t</wsdl:port>\n")
				   .append("    </wsdl:service>\n");
		} catch(SoapException e) {
			// Do nothing.
		}
		
		return sBuffer.toString();
	}
	
	public static String generateXsd(String registryName, String url, String host)
	{
		Class<?> soapClass = SoapRegistry.getDefault().getTopLevelUrlClass(registryName, url);
		
		if (soapClass == null) {
			return generateFailure("SOAP-ENV:Server", "Soap registry or URL not found.", null);
		}
		
		StringBuffer sBuffer = new StringBuffer();
		String simpleName = soapClass.getName();
		
		if (simpleName.indexOf(".") != -1) {
			simpleName = simpleName.substring(simpleName.lastIndexOf(".") + 1);
		}
		
		// WSDL Definition
		
		sBuffer.append("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n")
			   .append(generateSchema(soapClass, host, url, true));
		
		return sBuffer.toString();
	}
	
	/**
	 * Generates a WSDL response for a requested URL.
	 * 
	 * @param registryName <code>String</code> containing the registry name to look up.
	 * @param url <code>String</code> containing the requested URL for the SOAP service.
	 * @param host <code>String</code> containing the current server's host name and port.
	 * @return <code>String</code> containing the resulting SOAP WSDL document.
	 */
	public static String generateWsdl(String registryName, String url, String host)
	{
		Class<?> soapClass = SoapRegistry.getDefault().getTopLevelUrlClass(registryName, url);
		
		if (soapClass == null) {
			return generateFailure("SOAP-ENV:Server", "Soap registry or URL not found.", null);
		}
		
		StringBuffer sBuffer = new StringBuffer();
		String simpleName = soapClass.getName();
		
		if (simpleName.indexOf(".") != -1) {
			simpleName = simpleName.substring(simpleName.lastIndexOf(".") + 1);
		}
		
		sBuffer.append("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n")
			   .append("<wsdl:definitions xmlns:http=\"http://schemas.xmlsoap.org/wsdl/http/\"\n")
			   .append("\txmlns:soap=\"http://schemas.xmlsoap.org/wsdl/soap/\"\n")
			   .append("\txmlns:s=\"http://www.w3.org/2001/XMLSchema\"\n")
			   .append("\txmlns:soapenc=\"http://schemas.xmlsoap.org/soap/encoding/\"\n")
			   .append("\txmlns:tns=\"http://")
			   .append(host)
			   .append("/\"\n")
			   .append("\txmlns:tm=\"http://microsoft.com/wsdl/mime/textMatching/\"\n")
			   .append("\txmlns:mime=\"http://schemas.xmlsoap.org/wsdl/mime/\"\n")
			   .append("\ttargetNamespace=\"http://")
			   .append(host)
			   .append("/\"\n")
			   .append("\txmlns:wsdl=\"http://schemas.xmlsoap.org/wsdl/\">\n")
			   .append(generateSchema(soapClass, host, url, false))
			   .append("\n")
			   .append(generateMessages(soapClass, host, url))
			   .append(generatePorts(soapClass, host, url))
			   .append(generateBindings(soapClass, host, url))
			   .append(generateService(soapClass, host, url))
			   .append("</wsdl:definitions>\n");
		
		return sBuffer.toString();
	}
	
	/**
	 * Generates a response to a request made by a client.
	 * 
	 * @param soapClass <code>Class&lt;?&gt;</code> to refer to.
	 * @param soapMethod The <code>Method</code> requested.
	 * @param response <code>Object</code> containing the response object type.
	 * @param host <code>String</code> containing the host:port requested from the server.
	 * @return <code>String</code> containing the resulting response.
	 */
	public static String generateResponse(Class<?> soapClass, Method soapMethod, Object response, String host)
	{
		StringBuffer sBuffer = new StringBuffer();
		
		sBuffer.append("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n")
			   .append("<SOAP-ENV:Envelope xmlns:SOAP-ENV=\"http://schemas.xmlsoap.org/soap/envelope/\"\n")
			   .append("\txmlns:xsd=\"http://www.w3.org/2001/XMLSchema\"\n")
			   .append("\txmlns:http=\"http://schemas.xmlsoap.org/wsdl/http/\"\n")
		   	   .append("\txmlns:soap=\"http://schemas.xmlsoap.org/wsdl/soap/\"\n")
		   	   .append("\txmlns:s=\"http://www.w3.org/2001/XMLSchema\"\n")
		   	   .append("\txmlns:soapenc=\"http://schemas.xmlsoap.org/soap/encoding/\"\n")
		   	   .append("\txmlns:tns=\"http://")
		   	   .append(host)
		   	   .append("/\"\n")
		   	   .append("\txmlns:tm=\"http://microsoft.com/wsdl/mime/textMatching/\"\n")
		   	   .append("\txmlns:mime=\"http://schemas.xmlsoap.org/wsdl/mime/\"\n")
		   	   .append("\ttargetNamespace=\"http://")
		   	   .append(host)
		   	   .append("/\"\n")
		   	   .append("\txmlns:wsdl=\"http://schemas.xmlsoap.org/wsdl/\">\n")
		   	   .append("    <SOAP-ENV:Body>\n")
		   	   .append("\t<Get")
		   	   .append(soapMethod.getName())
		   	   .append("Response xmlns=\"http://")
		   	   .append(host)
		   	   .append("\">\n")
		   	   .append("\t    <response>");
		
		if (response.getClass().getName().contains("String")) {
			sBuffer.append((String) response);
		} else if (response.getClass().getName().contains("Integer")) {
			sBuffer.append(((Integer) response).intValue());
		} else if (response.getClass().getName().contains("Float")) {
			sBuffer.append(((Float) response).floatValue());
		} else if (response.getClass().getName().contains("Double")) {
			sBuffer.append(((Double) response).doubleValue());
		} else if (soapMethod.getReturnType().isEnum()) {
			sBuffer.append(response.toString());
		} else if (soapMethod.getReturnType().isArray()) {
			sBuffer.append("ARRAY");
		}
		
		sBuffer.append("</response>\n")
			   .append("\t</Get")
			   .append(soapMethod.getName())
			   .append("Response>\n")
			   .append("    </SOAP-ENV:Body>\n")
			   .append("</SOAP-ENV:Envelope>\n");
		
		return sBuffer.toString();
	}
}